package com.ehi.common.jersey.filter;

import com.ehi.common.jersey.status.DefaultExceptionMapper;
import com.ehi.common.jersey.util.RestClientService;
import com.ehi.common.test.TestPorts;
import com.ehi.common.test.jersey.AbstractLightHttpWithMemcachedIntTest;
import com.github.dreamhead.moco.HttpServer;
import com.github.dreamhead.moco.Moco;
import com.github.dreamhead.moco.Runner;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

import static com.github.dreamhead.moco.Moco.*;
import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class SecurityResourceFilterIntTest extends AbstractLightHttpWithMemcachedIntTest {

    private static Runner runner;

    @BeforeClass
    public static void startMoco() {
        HttpServer webConnect = httpServer(TestPorts.getMocoPort());

        webConnect.request(by(uri("/authenticated-session/17abdba4f6142e9b022ad2dee22765512d794096"))).response(
            "{\"token\":\"17abdba4f6142e9b022ad2dee22765512d794096\",\"userProfileId\":\"1000000001\","
                + "\"expires\":1404810055929,\"accessCount\":2,\"created\":1404808255913,\"updated\":1404808255929}");
        webConnect.request(by(uri("/authenticated-session/invalidTokenId"))).response(Moco.status(401));

        runner = Runner.runner(webConnect);
        runner.start();
    }

    @AfterClass
    public static void stopMoco() {
        try {
            runner.stop();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    @Override
    protected Application configureResource() {
        ResourceConfig rc = new ResourceConfig(SecurityResource.class);

        ServiceLocator mockServiceLocator = mockServiceLocator();
        SecurityResourceFilter filter = new SecurityResourceFilter(mockServiceLocator);

        rc.register(filter);
        rc.register(RolesAllowedDynamicFeature.class);
        rc.register(DefaultExceptionMapper.class);
        rc.property("contextConfigLocation", "classpath:applicationContext.xml");
        return rc;
    }

    private ServiceLocator mockServiceLocator() {
        ServiceLocator mockServiceLocator = mock(ServiceLocator.class);
        AbstractApplicationContext mockAbstractApplicationContext = mock(AbstractApplicationContext.class);
        ConfigurableListableBeanFactory mockConfigurableListableBeanFactory = mock(ConfigurableListableBeanFactory.class);
        when(mockAbstractApplicationContext.getBeanFactory()).thenReturn(mockConfigurableListableBeanFactory);
        when(mockServiceLocator.getService(ApplicationContext.class)).thenReturn(mockAbstractApplicationContext);
        when(mockConfigurableListableBeanFactory.resolveEmbeddedValue("${authenticated-session-service-location}"))
            .thenReturn("http://localhost:" + TestPorts.getMocoPort());
        when(mockConfigurableListableBeanFactory.getBean(RestClientService.class)).thenReturn(new RestClientService());
        return mockServiceLocator;
    }

    @Test
    public void shouldRequestUnrestrictedResourceOk() {
        Response response = target("/security-resource/unrestricted").request()
            .header("Authorization", "ISTORE_API ehi_visitor_id=\"A00B\"")
            .get();
        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.readEntity(String.class)).isEqualTo("A00B");
    }

    @Test
    public void shouldForbiddenWhenRequestRestrictedResourceWithoutToken() {
        Response response = target("/security-resource/restricted").request().get();
        assertThat(response.getStatus()).isEqualTo(403);
        assertThatJson(response.readEntity(String.class)).node("error.message").isEqualTo("Access forbidden.");
    }

    @Test
    public void shouldForbiddenWhenRequestRestrictedResourceWithInvalidToken() {
        Response response = target("/security-resource/restricted").request().get();
        assertThat(response.getStatus()).isEqualTo(403);
        assertThatJson(response.readEntity(String.class)).node("error.message").isEqualTo("Access forbidden.");
    }

    @Test
    public void shouldRequestRestrictedResourceOkWithValidToken() {
        Response response = target("/security-resource/restricted").request()
            .header("Authorization", "ISTORE_API istore_token=\"17abdba4f6142e9b022ad2dee22765512d794096\", ehi_visitor_id=\"A00B\"")
            .get();
        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.readEntity(String.class)).isEqualTo("1000000001");
    }

    @PermitAll
    @Path("/security-resource")
    public static class SecurityResource {

        @GET
        @Path("/unrestricted")
        public Response unrestricted(@Context SecurityContext securityContext) {
            return Response.ok().entity(securityContext.getUserPrincipal().getName()).build();
        }

        @GET
        @Path("/restricted")
        @RolesAllowed(User.SIGNIN_USER)
        public Response restricted(@Context SecurityContext securityContext) {
            return Response.ok().entity(securityContext.getUserPrincipal().getName()).build();
        }
    }

}
